<?php

namespace AppBundle\Controller;

use AdminBundle\Entity\HuobiAlertSender;
use AdminBundle\Entity\HuobiAlertTimestamp;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

abstract class HuobiKlineBaseController extends HuobiBaseController
{
    private $kline_calc_min_size = 30;

    abstract protected function KlineInstance($data_now, $prefix);

    abstract protected function KlineCalcInstance($data);

    abstract protected function getKline($coin, $timestamp = 0, $duration = 60);

    abstract protected function klinePeriod();

    abstract protected function cacheKey();


    protected function receiveKline(Request $request)
    {
        $content = $request->getContent();

        $data = json_decode($content, true);

        if ($data == null || !isset($data['ch']) || !isset($data['tick'])) {
            return $this->apiResult(1, 'empty data.', $content);
        }
// -------------------------------------------------------------------------------
//        $data = $data ?: [];
//        $data_default = ["ch" => 'market.btcusdt.kline.1min', "ts" => time(),
//            "tick" => ["id" => time(), "open" => 0, "close" => 0, "low" => 0,
//                "high" => 0, "amount" => 0, "vol" => 0, "count" => 0]
//        ];
//        $data = array_replace_recursive($data_default, $data);
// -------------------------------------------------------------------------------

        $coin = explode('.', $data['ch'])[1];
        $tick = $data['tick'];

        $datetime = new \DateTime();
        $timestamp = $tick['id'];
        $datetime->setTimestamp($timestamp);

        $data_now = [
            // KLine
            'open' => $tick['open'],
            'close' => $tick['close'],
            'low' => $tick['low'],
            'high' => $tick['high'],
            'amount' => $tick['amount'],
            'vol' => $tick['vol'],
            'count' => $tick['count'],
            'time' => $datetime,
            'time_stamp' => $timestamp,
            // KDJ
            'k_5mins' => 50.0,
            'd_5mins' => 50.0,
            'k_10mins' => 50.0,
            'd_10mins' => 50.0,
            'rsi_5mins' => 50.0,
            'rsi_10mins' => 50.0,
            'rsi_20mins' => 50.0,
            'rsi_30mins' => 50.0,
        ];

        $huobiMarketKline = $this->KlineInstance($data_now, 'coin_');
        $huobiMarketKline->setCoin($coin)
            ->setTimeStamp($timestamp)
            ->setClientIp($request->getClientIp());


        $em = $this->getDoctrine()->getManager();
        // 获取近四小时(60*4分钟)的数据
        $data = $this->getKline($coin, $timestamp, 60 * 4);
//        $data = $em->getRepository('AdminBundle:HuobiMarketKline1min')->get60mins($coin, $timestamp, 60 * 4);

        if (isset($data[0])) {
            $data_0 = $data[0];
            if ($data_now['time_stamp'] == $data_0['time_stamp']) {
                return $this->apiResult(1, 'duplicate item.', '');
            }
        }

        // time 字段由DateTime 转换为时间格式化字符串(Y-m-d H:i:s)
        $data_now['time'] = $datetime->format('Y-m-d H:i:s');
        // 增加当前JSON数据到返回结果的第一个
        array_unshift($data, $data_now);

        // 计算60数据：统计汇总
        $result = $this->analysis($coin, $data);

        if (!empty($result)) {
            // 保存数据
            $huobiCalcKline = $this->KlineCalcInstance($result);
            $huobiCalcKline->setLow1min($data_now['low']);
            $huobiCalcKline->setHigh1min($data_now['high']);
            $em->persist($huobiCalcKline);

            $huobiMarketKline->setK5mins($result['k_5mins'])
                ->setD5mins($result['d_5mins'])
                ->setK10mins($result['k_10mins'])
                ->setD10mins($result['d_10mins'])
                ->setRsi5mins($result['rsi_5mins'])
                ->setRsi10mins($result['rsi_10mins'])
                ->setRsi20mins($result['rsi_20mins'])
                ->setRsi30mins($result['rsi_30mins']);
        }

        $em->persist($huobiMarketKline);
        $em->flush();

        if ($key = $this->cacheKey()) {
            $this->cache($key, $content);
        }

        // 价格报警操作
//        $response = $this->priceAlertAction($request,'function');
//        if ($response instanceof Response) {
//            return $response;
//        }
        return $this->apiResult(0, '', '');
    }

    /**
     * 数据存入队列缓存
     * @param string $key
     * @param string $content
     */
    protected function cache($key = 'list:kline_1min', $content = '')
    {
        try {
            // kline_1min 数据存入队列
            $redis = $this->get('huobi.redis');
            $result = $redis->lPush($key, $content);
            return $result;

        } catch (\RedisException $e) {
            $message = 'redis Exception.';
            return -1;
        }
    }

    protected function calcKlineMinSize()
    {
        return $this->kline_calc_min_size;
    }

    /**
     * 根据币种，时间，时长，为条件,获取数据库中需要计算的数据.
     * @param string $coin
     * @param int $timestamp
     * @param int $duration
     */
    protected function analysis($coin = 'btcusdt', &$data)
    {
        // 需要记算的分钟位置。例:1分钟,3分钟,5分钟,15分钟...
        $length = count($data);
        if ($length < $this->calcKlineMinSize())
            return [];

        // 设置当前结果(1min)的内容,并防止传入参数的时间(time_stamp,coin_time)与实际DB内容不符
        $data_1min = $data[0];
        $data_2min = $data[1];

        $result['coin'] = $coin;
        $result['coin_time'] = new \Datetime($data_1min['time']);
        $result['time_stamp'] = $data_1min['time_stamp'];
        $result['open_1min'] = $data_1min['open']; // 1分钟的前开盘价
        $result['close_1min'] = $data_1min['close']; // 1分钟的收盘价
        $result['amount_1min'] = $data_1min['amount'];

        $kline_period = $this->klinePeriod();
        foreach ($kline_period as $key => $min) {

            $min = $min > $length ? $length : $min;

            $data_Xmin = $data[$min - 1];

            $summary_Xmin = $this->summaryKline60mins($data, 0, $min);

            $result['open_' . $key . 'min'] = $data_Xmin['open']; // 开盘价
            $result['close_' . $key . 'min'] = $data_Xmin['close']; // 收盘价

            $result['amount_sum_' . $key . 'mins'] = $summary_Xmin['amount_sum'];
            $result['amount_max_' . $key . 'mins'] = $summary_Xmin['amount_max'];
            $result['down_count_' . $key . 'mins'] = $summary_Xmin['down_count'];
            $result['up_count_' . $key . 'mins'] = $summary_Xmin['up_count'];
            $result['close_max_' . $key . 'min'] = $summary_Xmin['close_max'];
            $result['close_min_' . $key . 'min'] = $summary_Xmin['close_min'];
            // Amplitude 涨跌累计振幅计算
            $result['up_' . $key . '_ratio'] = $summary_Xmin['up_ratio_sum'];
            $result['down_' . $key . '_ratio'] = $summary_Xmin['down_ratio_sum'];

            // MA 计算
            $result['ma_' . $key . 'mins'] = $summary_Xmin['close_avg'];

            // RSI 计算
            $rsi = 50;
            $x = 0;
            $y = 1;

            if (isset($data_2min['k_' . $key . 'mins']) && isset($data_2min['d_' . $key . 'mins'])) {
                $last_close = $data_1min['close'] - $data_2min['close'];
                $x = $this->sma(max($last_close, 0.0), $min, 1, $data_2min['k_' . $key . 'mins']);
                $y = $this->sma(abs($last_close), $min, 1, $data_2min['d_' . $key . 'mins']);
                $rsi = $x / $y * 100;
            }
            // boll 30
            if (in_array($key, [30,])) {
                $rsi = $this->bollStandardDeviation($key, $result['ma_' . $key . 'mins'], $data);
            }
            if (in_array($key, [10,])) {
                $result['rsv_' . $key . 'mins'] = $this->bollStandardDeviation($key, $result['ma_' . $key . 'mins'], $data);;
            }
            $result['rsi_' . $key . 'mins'] = $rsi;
            $result['k_' . $key . 'mins'] = $x;
            $result['d_' . $key . 'mins'] = $y;


            /*
                        $sum = $summary_Xmin['up_sum'] + $summary_Xmin['down_sum'];
                        $rsi = $summary_Xmin['up_sum'] / ($sum ?: 1) * 100;

                        if (isset($data_2min['rsi_' . $key . 'mins'])) {
                            $rsi_previous = $data_2min['rsi_' . $key . 'mins'];
                        } else {
                            $rsi_previous = 50;
                        }

            //            $result['rsi_' . $key . 'mins'] = $rsi_previous * 2 / 3 + $rsi / 3;
                        $result['rsi_' . $key . 'mins'] = ($rsi_previous * ($min - 1) + $rsi) / $min;

                        $result['k_' . $key . 'mins'] = $k;
                        $result['d_' . $key . 'mins'] = $d;
                        // KDJ 计算
                        if (isset($data_1min['k_' . $key . 'mins']) && isset($data_1min['d_' . $key . 'mins'])) {
                            $k_0 = $data_2min['k_' . $key . 'mins'] ?: 50;
                            $d_0 = $data_2min['d_' . $key . 'mins'] ?: 50;
                        } else {
                            $k_0 = $d_0 = 50.0;
                        }

                        $numerator = $data_1min['close'] - $summary_Xmin['lowest'];
                        $denominator = $summary_Xmin['highest'] - $summary_Xmin['lowest'];
                        $denominator = $denominator ?: 1;
                        $rsv = $numerator / $denominator * 100;

            //            $k = 2 / 3 * $k_0 + $rsv / 3;
            //            $d = 2 / 3 * $d_0 + $k / 3;
                        $k = 4 / 5 * $k_0 + $rsv / 5;
                        $d = 4 / 4 * $d_0 + $k / 5;
                        $j = 3 * $k - 2 * $d;

                        $result['k_' . $key . 'mins'] = $k;
                        $result['d_' . $key . 'mins'] = $d;
                        $result['j_' . $key . 'mins'] = $j;
                        $result['rsv_' . $key . 'mins'] = $rsv;
            */

        }

        return $result;
    }

    /**
     * boll 标准差计算
     * @param $n 数量
     * @param $mb 均值
     * @param $data 数组
     * @return float|int
     */
    protected function bollStandardDeviation($n, $mb, &$data)
    {
        $pow_sum = array_reduce(
            array_slice($data, 0, $n),
            function ($v0, $v1) use ($mb) {
                return $v0 + pow($v1['close'] - $mb, 2);
            },
            0.0);
        $value = sqrt($pow_sum / $n);
        return $value;
    }

    /**
     * 【SMA】：求移动平均
     * 用法：SMA(X,N,M),求X的N日移动平均，M为权重。
     * 算法：若Y=SMA(X,N,M)，则 Y=[M*X+(N-M)*Y']/N,其中Y'表示上一周期Y值,N必须大于M。
     * 例如：SMA(CLOSE,30,1) 表示求30日移动平均价。
     * @param $x
     * @param $n
     * @param $m
     * @param $smaX 上一周期SMA值
     * @return float|int
     */

    protected function sma($x, $n, $m, $smaX = 0)
    {
        if ($n > $m) {
            $sma = ($m * $x + ($n - $m) * $smaX) / $n;
            return $sma;
        }
        return $x;
    }

    /**
     * 计算60分钟内的区间内的统计汇总数据
     * @param $data 60分钟数据
     * @param int $start 开始位置
     * @param int $end 结束位置
     * @return array 统计汇数据
     */
    protected function summaryKline60mins(&$data, $start = 0, $end = -1)
    {
        $length = count($data);
        $end = $end > 0 ? ($end > $length ? $length : $end) : $length;

        $result = array(
            'up_count' => 0,// 涨幅记数器
            'down_count' => 0, // 跌幅记数器
            'up_sum' => 0.0,// 涨幅之和
            'down_sum' => 0.0,//跌幅之和
            'amount_sum' => 0.0, // 交易数量求和值
            'amount_avg' => 0.0, // 交易数量平均值
            'amount_max' => 0.0, // 交易数量最大值
            'amount_min' => PHP_INT_MAX, // 交易数量最小值
            'open_max' => 0.0, // 开盘最大值
            'open_min' => PHP_INT_MAX, // 开盘最小值
            'close_max' => 0.0, // 收盘最大值
            'close_min' => PHP_INT_MAX, // 收盘最小值
            'close_sum' => 0.0,
            'close_avg' => 0.0,
            'pos_start' => $start,
            'pos_end' => $end,
            'close' => 0,
            'lowest' => PHP_INT_MAX,
            'highest' => 0.0,
            'down_ratio_sum' => 0.0,
            'up_ratio_sum' => 0.0,

        );
        if (isset($data[$start])) {
            $result['close'] = $data[$start]['close'];
        }
        for ($i = $start; $i < $end; $i++) {
            $coin_data = $data[$i];
            $prev_data = isset($data[$i + 1]) ? $data[$i + 1] : $data[$i];

            // 涨跌幅记数器
            if ($coin_data['close'] < $prev_data['close']) {
                $result['down_count']++;
                $d = $prev_data['close'] - $coin_data['close'];
                $result['down_sum'] += $d;
                $result['down_ratio_sum'] += $d / $prev_data['close'] * 100;
            } else {
                $result['up_count']++;
                $d = $coin_data['close'] - $prev_data['close'];
                $result['up_sum'] += $d;
                $result['up_ratio_sum'] += $d / $prev_data['close'] * 100;
            }

            $result['close_sum'] += $coin_data['close'];
            // 交易数量求和值
            $result['amount_sum'] += $coin_data['amount'];
            // 交易数量最大值
            if ($coin_data['amount'] > $result['amount_max']) {
                $result['amount_max'] = $coin_data['amount'];
            }
            // 交易数量最小值
            if ($coin_data['amount'] < $result['amount_min']) {
                $result['amount_min'] = $coin_data['amount'];
            }
            // 开盘最大值
            if ($coin_data['open'] > $result['open_max']) {
                $result['open_max'] = $coin_data['open'];
            }
            // 开盘最小值
            if ($coin_data['open'] < $result['open_min']) {
                $result['open_min'] = $coin_data['open'];
            }
            // 收盘最大值
            if ($coin_data['close'] > $result['close_max']) {
                $result['close_max'] = $coin_data['close'];
            }
            // 收盘最小值
            if ($coin_data['close'] < $result['close_min']) {
                $result['close_min'] = $coin_data['close'];
            }
            // 最低价格
            if ($coin_data['low'] < $result['lowest']) {
                $result['lowest'] = $coin_data['low'];
            }
            // 最高价格
            if ($coin_data['high'] > $result['highest']) {
                $result['highest'] = $coin_data['high'];
            }
        }
        // 交易数量平均值
        $result['amount_avg'] = $result['amount_sum'] / ($i ?: 1);
        $result['close_avg'] = $result['close_sum'] / ($i ?: 1);
        $result['time_start'] = $data[$start]['time'];
        $result['time_end'] = $data[$end - 1]['time'];

        return $result;
    }

}
